package com.sunshuo.springsecuritydemo.config;

import com.sunshuo.springsecuritydemo.access.DynamicFilterSecurityInterceptor;
import com.sunshuo.springsecuritydemo.filter.VerifyCodeFilter;
import com.sunshuo.springsecuritydemo.handler.LoginFailureHandler;
import com.sunshuo.springsecuritydemo.handler.LoginSuccessHandler;
import com.sunshuo.springsecuritydemo.service.UserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.session.HttpSessionEventPublisher;

import javax.sql.DataSource;

/**
 * @ClassName WebSecurityConfig
 * @Description
 * @Author sunshuo
 * @Date 2021/11/6 17:16
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /* 登录成功后处理器 */
    private LoginSuccessHandler loginSuccessHandler;

    /* 登录失败后处理器 */
    private LoginFailureHandler loginFailureHandler;

    /* 数据源 */
    private DataSource dataSource;

    /* 验证码过滤器 */
    private VerifyCodeFilter verifyCodeFilter;

    /* 动态权限过滤器 */
    private DynamicFilterSecurityInterceptor dynamicFilterSecurityInterceptor;

    /* Session并发下失效后的处理策略 */
    private CustomExpiredSessionStrategy customExpiredSessionStrategy;

    @Autowired
    public void setCustomExpiredSessionStrategy(CustomExpiredSessionStrategy customExpiredSessionStrategy) {
        this.customExpiredSessionStrategy = customExpiredSessionStrategy;
    }

    @Autowired
    public void setDynamicFilterSecurityInterceptor(DynamicFilterSecurityInterceptor dynamicFilterSecurityInterceptor) {
        this.dynamicFilterSecurityInterceptor = dynamicFilterSecurityInterceptor;
    }

    @Autowired
    public void setVerifyCodeFilter(VerifyCodeFilter verifyCodeFilter) {
        this.verifyCodeFilter = verifyCodeFilter;
    }

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Autowired
    public void setLoginFailureHandler(LoginFailureHandler loginFailureHandler) {
        this.loginFailureHandler = loginFailureHandler;
    }

    @Autowired
    public void setLoginSuccessHandler(LoginSuccessHandler loginSuccessHandler) {
        this.loginSuccessHandler = loginSuccessHandler;
    }

    /**
     * 配置密码编码器
     * @return 返回密码编码器实例
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * “配置从数据库中获取记住我”token
     * @return PersistentTokenRepository
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }

    /**
     * 监听session创建及销毁事件，来及时清理session的记录，以确保最新的session状态可以被及时感知到。
     * @return HttpSessionEventPublisher
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    /**
     * 加载用户信息的Service
     * @return UserDetailsService
     */
    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailService();
    }

    /**
     * 定义角色的继承层次
     * @return RoleHierarchy
     */
    @Bean
    public RoleHierarchy roleHierarchy() {
        String hierarchyPattern = "ROLE_ADMIN > ROLE_DIRECTOR " + System.lineSeparator() + " ROLE_DIRECTOR > ROLE_VISITOR";
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy(hierarchyPattern);
        return roleHierarchy;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 忽略静态资源
        web.ignoring().antMatchers("/css/**", "/js/**", "/images/**", "/font/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                    // 禁用csrf保护
                    .csrf().disable()
                // csrf 采用CookieCsrfTokenRepository方式
                // .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
                    // 添加自定义的验证码过滤器，在执行用户名密码验证的过滤之前执行
                    .addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class)
                    // 允许页面嵌套
                    .headers().frameOptions().disable()
                .and()
                    // 自定义登录页面
                    .formLogin() // 允许表单登录
                    .loginPage("/index.html") // 登录页面，spring security会用重定向方式跳转到/index.html
                    .loginProcessingUrl("/login") // 处理登录请求得URL
                    .successHandler(loginSuccessHandler) // 登录成功的handler
                    .failureHandler(loginFailureHandler) // 登录失败后
                .and()
                    .authorizeRequests()
                    // 鉴权
                    .antMatchers("/resource/res_1").hasAuthority("5000")
                    .antMatchers("/resource/res_2").hasAnyAuthority("5000", "6000")
                    .antMatchers("/resource/res_3").access("hasAuthority('7000') or hasRole('DIRECTOR')")
                    .antMatchers("/resource/**").authenticated()
                    .antMatchers("/index.html", "/login", "/verify_image").permitAll() // 对于登录页面是允许访问的，登录请求也是允许的
                    .anyRequest().authenticated() // 除登录页面之外的页面，需要认证后访问
                // 登出配置
                .and()
                    .logout()
                    .logoutUrl("/logout")
                    .deleteCookies("JSESSIONID") // 删除会话ID
                .and()
                // 会话控制
                    .sessionManagement()
                    .invalidSessionUrl("/index.html?error=INVALID_SESSION") // session失效时，跳转的url
                    .maximumSessions(1) // 该账号在同一时间内只能有一个有效的登录
//                    .maxSessionsPreventsLogin(true) // 达到maximumSessions最大数量时阻止登录
                    .expiredSessionStrategy(customExpiredSessionStrategy) // session并发失效策略
                .and().and()
                // 无权限时跳转到403页面
                    .exceptionHandling().accessDeniedPage("/403.jsp")
                .and()
                    // 记住我功能
                    .rememberMe()
                    .rememberMeParameter("rememberMe") // 表单提交时的参数
                    .rememberMeCookieName("cookie-rem-me") // 保存在浏览器端的cookie名称
                    .tokenValiditySeconds(7 * 24 * 60 * 60) // 设置免登录时长，单位为秒
                    .tokenRepository(persistentTokenRepository()); // 从数据库中获取token
        // 配置动态权限过滤器
        http.addFilterAfter(dynamicFilterSecurityInterceptor, FilterSecurityInterceptor.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }
}
